/*
 * Copyright 2015 Hannes Dorfmann.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hannesdorfmann.mosby3;

import com.hannesdorfmann.mosby3.mvi.MviPresenter;
import com.hannesdorfmann.mosby3.mvp.MvpPresenter;
import com.hannesdorfmann.mosby3.mvp.MvpView;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.utils.PacMap;

import java.util.UUID;

/**
 * The concrete implementation of {@link AbilityMviDelegate}.
 * This delegate creates the Presenter and attaches the View to the presenter in {@link
 * Ability#onStart(Intent)}. The view is detached from presenter in {@link
 * Ability#onStop()}
 *
 * @param <V> The type of {@link MvpView}
 * @param <P> The type of {@link MvpPresenter}
 * @see AbilityMviDelegate
 *
 * @author Hannes Dorfmann
 */
public class AbilityMviDelegateImpl<V extends MvpView, P extends MviPresenter<V, ?>>
    implements AbilityMviDelegate {


  public final static boolean DEBUG = false;
  private static final String TAG = "ActivityMviDelegateImpl";
  private static final HiLogLabel DEBUG_TAG = new HiLogLabel(HiLog.LOG_APP, 0, TAG);
  private static final String KEY_MOSBY_VIEW_ID = "com.hannesdorfmann.mosby3.activity.mvi.id";
  private String mosbyViewId = null;

  private MviDelegateCallback<V, P> delegateCallback;
  private Ability activity;
  private boolean keepPresenterInstance;
  private P presenter;

  /**
   * Creates a new Delegate with an presenter that survives screen orientation changes
   *
   * @param activity The activity
   * @param delegateCallback The delegate callback
   */
  public AbilityMviDelegateImpl( Ability activity,
       MviDelegateCallback<V, P> delegateCallback) {
    this(activity, delegateCallback, true);
  }

  /**
   * Creates a new delegate
   *
   * @param activity The activity
   * @param delegateCallback The delegate callback
   * @param keepPresenterInstance true, if the presenter instance should be kept through screen
   * orientation changes, false if not (a new presenter instance will be created every time you
   * rotate your device)
   */
  public AbilityMviDelegateImpl( Ability activity,
       MviDelegateCallback<V, P> delegateCallback, boolean keepPresenterInstance) {

    if (activity == null) {
      throw new NullPointerException("Activity is null");
    }

    if (delegateCallback == null) {
      throw new NullPointerException("MvpDelegateCallback is null!");
    }
    this.delegateCallback = delegateCallback;
    this.activity = activity;
    this.keepPresenterInstance = keepPresenterInstance;
  }

  /**
   * Generates the unique (mosby internal) view id and calls {@link
   * MviDelegateCallback#createPresenter()}
   * to create a new presenter instance
   *
   * @return The new created presenter instance
   */
  private P createViewIdAndCreatePresenter() {

    P presenter = delegateCallback.createPresenter();
    if (presenter == null) {
      throw new NullPointerException(
          "Presenter returned from createPresenter() is null. Activity is " + activity);
    }
    if (keepPresenterInstance) {
      mosbyViewId = UUID.randomUUID().toString();
      PresenterManager.putPresenter(activity, mosbyViewId, presenter);
    }
    return presenter;
  }



  /**
   * Determines whether or not a Presenter Instance should be kept
   *
   * @param keepPresenterInstance true, if the delegate has enabled keep
   */
  static boolean retainPresenterInstance(boolean keepPresenterInstance, Ability activity) {
    return keepPresenterInstance && (activity.isUpdatingConfigurations()|| !activity.isTerminating());
  }

  @Override
  public void onStart(Intent intent) {

    if (DEBUG) {
      HiLog.debug(DEBUG_TAG,
              "MosbyView ID = " + mosbyViewId + " for MvpView: " + delegateCallback.getMvpView());
    }
  }

  @Override
  public void onPostStart(PacMap pacMap) {

  }

  @Override
  public void onStop() {
    if (presenter != null) {
      // Presenter is only null if Activity.finish() called before Activity.onStart() has been reached
      boolean retainPresenterInstance = retainPresenterInstance(keepPresenterInstance, activity);
      if (!retainPresenterInstance) {
        presenter.destroy();
        if (mosbyViewId != null) { // mosbyViewId == null if keepPresenterInstance == false
          PresenterManager.remove(activity, mosbyViewId);
        }
        HiLog.debug(DEBUG_TAG, "Destroying Presenter permanently " + presenter);
      }

    }
    presenter = null;
    activity = null;
    delegateCallback = null;
  }

  @Override
  public void onActive() {
    boolean viewStateWillBeRestored = false;

    if (mosbyViewId == null) {
      // No presenter available,
      // Activity is starting for the first time (or keepPresenterInstance == false)
      presenter = createViewIdAndCreatePresenter();
      if (DEBUG) {
        HiLog.debug(DEBUG_TAG, "new Presenter instance created: "
                + presenter
                + " for "
                + delegateCallback.getMvpView());
      }
    } else {
      presenter = PresenterManager.getPresenter(activity, mosbyViewId);
      if (presenter == null) {
        // Process death,
        // hence no presenter with the given viewState id stored, although we have a viewState id
        presenter = createViewIdAndCreatePresenter();
        if (DEBUG) {
          HiLog.debug(DEBUG_TAG,
                  "No Presenter instance found in cache, although MosbyView ID present. This was caused by process death, therefore new Presenter instance created: "
                          + presenter);
        }
      } else {
        viewStateWillBeRestored = true;
        if (DEBUG) {
          HiLog.debug(DEBUG_TAG, "Presenter instance reused from internal cache: " + presenter);
        }
      }
    }

    // presenter is ready, so attach viewState
    V view = delegateCallback.getMvpView();
    if (view == null) {
      throw new NullPointerException(
              "MvpView returned from getMvpView() is null. Returned by " + activity);
    }

    if (viewStateWillBeRestored) {
      delegateCallback.setRestoringViewState(true);
    }

    presenter.attachView(view);

    if (viewStateWillBeRestored) {
      delegateCallback.setRestoringViewState(false);
    }

    if (DEBUG) {
      HiLog.debug(DEBUG_TAG,
              "MvpView attached to Presenter. MvpView: " + view + "   Presenter: " + presenter);
    }
  }

  @Override
  public void onPostActive() {

  }

  @Override
  public void onInactive() {

  }

  @Override
  public void onForeground(Intent intent) {

  }

  @Override
  public void onBackground() {
    presenter.detachView();

    if (DEBUG) {
      HiLog.debug(DEBUG_TAG, "detached MvpView from Presenter. MvpView "
              + delegateCallback.getMvpView()
              + "   Presenter: "
              + presenter);
    }
  }

  @Override
  public void onSaveAbilityState(PacMap outState) {
    if (keepPresenterInstance && outState != null) {
      outState.putString(KEY_MOSBY_VIEW_ID, mosbyViewId);
      if (DEBUG) {
        HiLog.debug(DEBUG_TAG, "Saving MosbyViewId into Bundle. ViewId: " + mosbyViewId);
      }
    }

  }

  @Override
  public Object onStoreDataWhenConfigChange() {
    return null;
  }


}
